package networks.protocol;

import java.net.Socket;
import com.alibaba.fastjson.JSONObject;
import networks.client.SocketClient;
import networks.event.Delegate;

enum ProtocolState {

    NONE((byte) 0), // none
    START((byte) 1), // Just open, need to send handshaking
    HANDSHAKING((byte) 2), // on handshaking process
    WORKING((byte) 3), // can receive and send dataj
    CLOSED((byte) 4); // on read body

    private final byte _value;

    private ProtocolState(byte value) {
        _value = value;
    }

    public byte ToByte() {
        return _value;
    }

    public static ProtocolState ToEnum(byte value) {
        for (ProtocolState e : ProtocolState.values()) {
            if (e.ToByte() == value)
                return e;
        }
        return ProtocolState.NONE;
    }
}

public class Protocol {

    private ProtocolState _state;
    private MessageProtocol _messageProtocol;
    private Transporter _transporter;
    private HandshakeService _handshakeService;
    private HeartbeatService _heartbeatService;

    private SocketClient _client;

    public Protocol(SocketClient client, Socket socket) {
        _client = client;
        _transporter = new Transporter(socket, new Delegate.ActionOne<Package>() {
            @Override
            public void Invoke(Package o) {
                processMessage(o);
            }
        });
        _transporter.onDisconnect = new Delegate.Action() {
            @Override
            public void Invoke() {
                onDisconnect();
            }
        };
        _handshakeService = new HandshakeService(this);
        _state = ProtocolState.START;
    }

    public SocketClient getClient() {
        return _client;
    }

    public void Start(Delegate.Action callback) {
        _transporter.Start();
        _handshakeService.Request(callback);
        _state = ProtocolState.HANDSHAKING;
    }

    /**
     * Send system message. This method only used to send system package
     * 
     * @param type
     */
    public void Send(PackageType type) {
        if (type == PackageType.PKG_MESSAGE) {
            return;
        }

        _transporter.Send(PackageProtocol.Encode(type));
    }

    /**
     * Send system message, these message do not use messageProtocol. This method
     * only used to send system package
     * 
     * @param type
     * @param msg
     */
    public void Send(PackageType type, String msg) {
        if (type == PackageType.PKG_MESSAGE)
            return;

        byte[] body = msg.getBytes();
        _transporter.Send(PackageProtocol.Encode(type, body));
    }

    /**
     * Send notify, do not need id
     * 
     * @param route
     * @param msg
     */
    public void Send(String route, Object msg) {
        Send(route, (byte) 0, msg);
    }

    /**
     * Send request, user request id
     * 
     * @param route
     * @param id
     * @param msg
     */
    public void Send(String route, byte id, Object msg) {
        if (_state != ProtocolState.WORKING) {
            return;
        }

        byte[] body = _messageProtocol.Encode(route, id, msg);
        send(PackageType.PKG_MESSAGE, body);
    }

    /**
     * Send message use the transporter
     * 
     * @param type
     * @param body
     */
    private void send(PackageType type, byte[] body) {
        if (_state == ProtocolState.CLOSED) {
            return;
        }

        _transporter.Send(PackageProtocol.Encode(type, body));
    }

    private void processMessage(Package pkg) {
        // Ignore all the message except handshading at handshake stage
        if (pkg.type == PackageType.PKG_HANDSHAKE && _state == ProtocolState.HANDSHAKING) // 握手回调
        {
            String handshakeStr = new String(pkg.body);
            // System.out.println("握手 " + handshakeStr);
            processHandshakeData(handshakeStr);
            _state = ProtocolState.WORKING;
        } else if (pkg.type == PackageType.PKG_HEARTBEAT && _state == ProtocolState.WORKING) // 心跳回调
        {
            // System.out.println("心跳");
            _heartbeatService.ResetTimeout();
        } else if (pkg.type == PackageType.PKG_MESSAGE && _state == ProtocolState.WORKING) // 自定义消息
        {
            _heartbeatService.ResetTimeout();
            _client.ProcessMessage(_messageProtocol.Decode(pkg.body));
        } else if (pkg.type == PackageType.PKG_KICK) {
            _client.Disconnect();
            Close();
        }
    }

    private void processHandshakeData(String msg) {
        JSONObject jo = JSONObject.parseObject(msg);
        Message_Handshake handshakeMsg = JSONObject.toJavaObject(jo, Message_Handshake.class);

        // Handshake error
        if (handshakeMsg == null) {
            throw new NullPointerException("Handshake error! Please check your handshake config.");
        }

        // Set compress data
        _messageProtocol = new MessageProtocol(handshakeMsg.route);

        // Init heartbeat service
        _heartbeatService = new HeartbeatService(handshakeMsg.heartbeat, this);
        if (handshakeMsg.heartbeat > 0) {
            _heartbeatService.Start();
        }

        // send ack and change protocol state
        _handshakeService.Ack();
        _state = ProtocolState.WORKING;

        // Invoke handshake callback
        _handshakeService.InvokeCallback(handshakeMsg);
    }

    private void onDisconnect() {
        _client.Disconnect();
    }

    public void Close() {
        _transporter.Close();
        _heartbeatService.Stop();
        _state = ProtocolState.CLOSED;
    }
}
